Inheritance is useful, but another way to do the exact same thing is just to use other classes and modules, rather than rely on implicit inheritance. If you look at the three ways to exploit inheritance, two of the three involve writing new code to replace or alter functionality. This can easily be replicated by just calling functions in a module. Here's an example of doing this:
In [ ]:
class Spa():
def __init__(self):
print("in init of spa")
class CoffeeShop():
def __init__(self):
print("in init of coffeeshop")
class Laundery():
def __init__(self):
print("in init of Laundery")
class Hotel():
def __init__(self):
print("Hotel init")
self.spa = Spa()
self.cs = CoffeeShop()
self.shop = Shop()
In [1]:
class Other():
def override(self):
print("OTHER override()")
def implicit(self):
print("OTHER implicit()")
def altered(self):
print("OTHER altered()")
def test(self):
print("Test")
class Child():
x = 12
def __init__(self):
Child.other = Other()
def implicit(self):
self.x = 11
self.other.implicit()
def override(self):
print("CHILD override()")
def altered(self):
print("CHILD, BEFORE OTHER altered()")
self.other.altered()
print("CHILD, AFTER OTHER altered()")
son = Child()
girl = Child()
son.other.test()
son.implicit()
print(girl.x)
son.override()
son.altered()
In [7]:
class Other():
a = 10
def override(self):
print("OTHER override()")
def implicit(self):
print("OTHER implicit()")
def altered(self):
print("OTHER altered()")
def test(self):
print("Test")
class Child():
x = 12
def __init__(self):
Child.other = Other()
def implicit(self):
self.x = 11
self.other.implicit()
def override(self):
print("CHILD override()")
def altered(self):
print("CHILD, BEFORE OTHER altered()")
self.other.altered()
print("CHILD, AFTER OTHER altered()")
son = Child()
girl = Child()
son.other.a = 1100
son.implicit()
print(girl.other.a)
print(id(son.other))
print(id(girl.other))
In [6]:
class Other():
a = 10
def override(self):
print("OTHER override()")
def implicit(self):
print("OTHER implicit()")
def altered(self):
print("OTHER altered()")
def test(self):
print("Test")
class Child():
x = 12
def __init__(self):
self.other = Other()
def implicit(self):
self.x = 11
self.other.implicit()
def override(self):
print("CHILD override()")
def altered(self):
print("CHILD, BEFORE OTHER altered()")
self.other.altered()
print("CHILD, AFTER OTHER altered()")
son = Child()
girl = Child()
son.other.a = 1100
son.implicit()
print(girl.other.a)
print(id(son.other))
print(id(girl.other))
The question of "inheritance versus composition" comes down to an attempt to solve the problem of reusable code. You don't want to have duplicated code all over your software, since that's not clean and efficient. Inheritance solves this problem by creating a mechanism for you to have implied features in base classes. Composition solves this by giving you modules and the ability to call functions in other classes.
If both solutions solve the problem of reuse, then which one is appropriate in which situations? The answer is incredibly subjective, but I'll give you my three guidelines for when to do which:
Do not be a slave to these rules. The thing to remember about object-oriented programming is that it is entirely a social convention programmers have created to package and share code. Because it's a social convention, but one that's codified in Python, you may be forced to avoid these rules because of the people you work with. In that case, find out how they use things and then just adapt to the situation.
In [ ]: